﻿using Microsoft.Extensions.Caching.Memory;
using StarsAdmin.Core.Attributes;

namespace StarsAdmin.Core.Cache
{
    [SingleInstance]
    public class CacheService : ICacheService
    {
        private readonly IMemoryCache _cache;

        public CacheService(IMemoryCache cache)
        {
            _cache = cache ?? throw new ArgumentNullException(nameof(cache));
        }

        /// <summary>
        /// 添加数据到本地缓存
        /// </summary>
        /// <param name="key">秘钥键</param>
        /// <param name="value">缓存值</param>
        /// <param name="expire">过期时间(分钟)</param>
        /// <exception cref="ArgumentNullException"></exception>
        public void Set(string key, object value, int expire = default)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException(nameof(key));
            }
            if (_cache.TryGetValue(key, out _))
            {
                _cache.Remove(key);
            }
            if (expire != default)
            {
                _cache.Set(key, value, new MemoryCacheEntryOptions()
                {
                    AbsoluteExpiration = DateTime.Now.AddMinutes(expire)
                });
            } else
            {
                _cache.Set(key, value);
            }
        }

        /// <summary>
        /// 异步添加数据到本地缓存
        /// </summary>
        /// <param name="key">秘钥键</param>
        /// <param name="value">缓存值</param>
        /// <param name="expire">过期时间(分钟)</param>
        public Task SetAsync(string key, object value, int expire = default)
        {
            Set(key, value, expire);
            return Task.CompletedTask;
        }

        /// <summary>
        /// 添加数据到本地缓存
        /// </summary>
        /// <param name="userId">用户Id</param>
        /// <param name="type">数据类别</param>
        /// <param name="value">缓存值</param>
        /// <param name="expire">过期时间(分钟)</param>
        /// <exception cref="ArgumentNullException"></exception>
        public void Set(long userId, string type, object value, int expire = default)
        {
            string key = $"{type}_{userId}";

            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException(nameof(key));
            }
            if (_cache.TryGetValue(key, out _))
            {
                _cache.Remove(key);
            }
            if (expire != default)
            {
                _cache.Set(key, value, new MemoryCacheEntryOptions()
                {
                    AbsoluteExpiration = DateTime.Now.AddMinutes(expire)
                });
            } else
            {
                _cache.Set(key, value);
            }
        }

        /// <summary>
        /// 异步添加数据到本地缓存
        /// </summary>
        /// <param name="userId">用户Id</param>
        /// <param name="type">数据类别</param>
        /// <param name="value">缓存值</param>
        /// <param name="expire">过期时间(分钟)</param>
        public Task SetAsync(long userId, string type, object value, int expire = default)
        {
            Set(userId, type, value, expire);
            return Task.CompletedTask;
        }

        /// <summary>
        /// 获得本地缓存中的数据
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="userId">用户Id</param>
        /// <param name="type">数据类别</param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        public T Get<T>(long userId, string type)
        {
            string key = $"{type}_{userId}";
            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException(nameof(key));
            }
            return _cache.Get<T>(key);
        }

        /// <summary>
        /// 异步获得本地缓存中的数据
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="userId">用户Id</param>
        /// <param name="type">数据类别</param>
        /// <returns></returns>
        public Task<T> GetAsync<T>(long userId, string type)
        {
            return Task.FromResult(Get<T>(userId, type));
        }

        /// <summary>
        /// 获得本地缓存中的数据
        /// </summary>
        /// <typeparam name="T">返回值类型</typeparam>
        /// <param name="key">秘钥键</param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        public T Get<T>(string key)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException(nameof(key));
            }
            return _cache.Get<T>(key);
        }

        /// <summary>
        /// 异步获得本地缓存中的数据
        /// </summary>
        /// <typeparam name="T">返回值类型</typeparam>
        /// <param name="key">秘钥键</param>
        /// <returns></returns>
        public Task<T> GetAsync<T>(string key)
        {
            return Task.FromResult(Get<T>(key));
        }

        /// <summary>
        /// 尝试获得指定键的值
        /// </summary>
        /// <param name="key">秘钥键</param>
        /// <param name="value">获得的返回值</param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        public bool TryGetValue(string key, out object? value)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException(nameof(key));
            }
            if (_cache.TryGetValue(key, out value))
            {
                return true;
            }
            return false;
        }

        /// <summary>
        /// 异步尝试获得指定键的值
        /// </summary>
        /// <param name="key">秘钥键</param>
        /// <param name="value">获得的返回值</param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        public Task<bool> TryGetValueAsync(string key, out object? value)
        {
            return Task.FromResult(TryGetValue(key, out value));
        }

        /// <summary>
        /// 异步获取或者设置指定键的值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">缓存键</param>
        /// <param name="func">回调函数</param>
        /// <param name="expire">过期时间(分钟)</param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        public T GetOrSet<T>(string key, Func<T> func, int expire = default)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException(nameof(key));
            }

            if (Validate(key))
            {
                try
                {
                    return Get<T>(key);
                } catch
                {
                    Remove(key);
                }
            }

            var result = func.Invoke();

            if (expire != default)
            {
                Set(key, result, expire);
            } else
            {
                Set(key, result);
            }
            return result;
        }

        /// <summary>
        /// 异步获取或者设置指定键的值
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="key">缓存键</param>
        /// <param name="func">回调函数</param>
        /// <param name="expire">过期时间(分钟)</param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        public async Task<T> GetOrSetAsync<T>(string key, Func<Task<T>> func, int expire = default)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException(nameof(key));
            }

            if (await ValidateAsync(key))
            {
                try
                {
                    return await GetAsync<T>(key);
                } catch
                {
                    await RemoveAsync(key);
                }
            }

            var result = await func.Invoke();

            if (expire != default)
            {
                await SetAsync(key, result, expire);
            } else
            {
                await SetAsync(key, result);
            }
            return result;
        }

        public async Task<T> GetOrSetAsync<T>(string key, long userId, Func<Task<T>> func, int expire = 0)
        {
            key = $"{key}_{userId}";

            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException(nameof(key));
            }
            return await GetOrSetAsync(key, func, expire);
        }

        /// <summary>
        ///  验证数据是否存在
        /// </summary>
        /// <param name="key">秘钥键</param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        public bool Validate(string key)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException(nameof(key));
            }
            if (_cache.TryGetValue(key, out _))
            {
                return true;
            }
            return false;
        }

        /// <summary>
        ///  异步验证数据是否存在
        /// </summary>
        /// <param name="key">秘钥键</param>
        /// <returns></returns>
        public Task<bool> ValidateAsync(string key)
        {
            return Task.FromResult(Validate(key));
        }

        /// <summary>
        ///  验证数据是否存在
        /// </summary>
        /// <param name="key">秘钥键</param>
        /// <param name="value">需要验证的数据</param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        public bool Validate(string key, object value)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException(nameof(key));
            }
            if (_cache.TryGetValue(key, out object? result))
            {
                if (result?.ToString() == value.ToString())
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        ///  异步验证数据是否存在
        /// </summary>
        /// <param name="key">秘钥键</param>
        /// <param name="value">需要验证的数据</param>
        /// <returns></returns>
        public Task<bool> ValidateAsync(string key, object value)
        {
            return Task.FromResult(Validate(key, value));
        }

        /// <summary>
        /// 验证数据是否存在
        /// </summary>
        /// <param name="userId">用户Id</param>
        /// <param name="type">数据类别</param>
        /// <param name="value">需要验证的数据</param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"></exception>
        public bool Validate(long userId, string type, object value)
        {
            string key = $"{type}_{userId}";
            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException(nameof(key));
            }
            if (_cache.TryGetValue(key, out object? result))
            {
                if (result?.ToString() == value.ToString())
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// 异步验证数据是否存在
        /// </summary>
        /// <param name="userId">用户Id</param>
        /// <param name="type">数据类别</param>
        /// <param name="value">需要验证的数据</param>
        /// <returns></returns>
        public Task<bool> ValidateAsync(long userId, string type, object value)
        {
            return Task.FromResult(Validate(userId, type, value));
        }

        /// <summary>
        /// 移除指定键的数据
        /// </summary>
        /// <param name="key">秘钥键</param>
        /// <exception cref="ArgumentNullException"></exception>
        public void Remove(string key)
        {
            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException(nameof(key));
            }
            _cache.Remove(key);
        }

        /// <summary>
        /// 异步移除指定键的数据
        /// </summary>
        /// <param name="key">秘钥键</param>
        public Task RemoveAsync(string key)
        {
            Remove(key);
            return Task.CompletedTask;
        }

        /// <summary>
        /// 移除指定的数据
        /// </summary>
        /// <param name="userId">用户Id</param>
        /// <param name="type">数据类别</param>
        /// <exception cref="ArgumentNullException"></exception>
        public void Remove(long userId, string type)
        {
            string key = $"{type}_{userId}";
            if (string.IsNullOrWhiteSpace(key))
            {
                throw new ArgumentNullException(nameof(key));
            }
            _cache.Remove(key);
        }

        /// <summary>
        /// 异步移除指定的数据
        /// </summary>
        /// <param name="userId">用户Id</param>
        /// <param name="type">数据类别</param>
        public Task RemoveAsync(long userId, string type)
        {
            Remove(userId, type);
            return Task.CompletedTask;
        }
    }
}